#[cfg(feature = "generate")]
fn main() {
    let b = proto_build::Boilerplate::create();
    let proto_build = b.resolve_flow_targets();

    prost_build::Config::new()
        .out_dir(&b.src_dir)
        .btree_map(&["."]) // Make ordering stable for snapshots.
        // Fields that hold tuple-encoded values use Bytes rather than Vec<u8>.
        .bytes(&[
            "internal",
            "key_packed",
            "partitions_packed",
            "values_packed",
        ])
        .file_descriptor_set_path(&b.descriptor_path)
        .compile_well_known_types()
        .extern_path(".consumer", "::proto_gazette::consumer")
        .extern_path(".google.protobuf", "::pbjson_types")
        .extern_path(".protocol", "::proto_gazette::broker")
        .extern_path(".recoverylog", "::proto_gazette::recoverylog")
        .compile_protos(&proto_build, &b.proto_include())
        .expect("failed to compile protobuf");

    pbjson_build::Builder::new()
        .out_dir(&b.src_dir)
        .register_descriptors(&std::fs::read(b.descriptor_path).expect("read descriptors"))
        .unwrap()
        .btree_map(["."]) // Make ordering stable for snapshots.
        .build(&[".flow", ".capture", ".derive", ".materialize", ".ops"])
        .expect("building pbjson");

    // Next, apply some fixups to the serde implementations generated by pbjson:
    // * Fields ending in "_json" are borrowed and serialized as &RawValue,
    //   and are deserialized into a Box<RawValue> that's converted to String.
    // * Fields ending in "_json_map" are mapped from BTreeMap<String, String>
    //   to BTreeMap<&str, &RawValue>, and deserialize in reverse.
    // * Fields ending in "_json_vec" are mapped from Vec<String> to Vec<&RawValue>,
    //   and deserialize in reverse as well.
    // * Our stats documents' "bytesTotal" field is typed as u64 to allow for
    //   tallying relatively large values in a single document but we do not want
    //   this value serialized as a string, so we remove the string conversion.

    let ser_json_re =
        regex::Regex::new(r#"struct_ser\.serialize_field\(".+", (&self\..*_json)\)\?;"#).unwrap();
    let ser_json_map_re =
        regex::Regex::new(r#"struct_ser\.serialize_field\(".+", (&self\..*_json_map)\)\?;"#)
            .unwrap();
    let ser_json_vec_re =
        regex::Regex::new(r#"struct_ser\.serialize_field\(".+", (&self\..*_json_vec)\)\?;"#)
            .unwrap();
    let ser_bytes_re =
        regex::Regex::new(r#"struct_ser\.serialize_field\("(bytesTotal|bytes)", ToString::to_string\(&self\.(bytes_total|bytes)\).as_str\(\)\)\?;"#)
            .unwrap();

    let de_json_re_1 = regex::Regex::new(r#"let mut .+_json__ (= None);"#).unwrap();
    let de_json_re_2 = regex::Regex::new(r#"_json__.(unwrap_or_default\(\)),"#).unwrap();
    let de_json_map_re_1 = regex::Regex::new(r#"let mut .+_json_map__ (= None);"#).unwrap();
    let de_json_map_re_2 = regex::Regex::new(r#"_json_map__.unwrap_or_default\(\)(),"#).unwrap();
    let de_json_vec_re_1 = regex::Regex::new(r#"let mut .+_json_vec__ (= None);"#).unwrap();
    let de_json_vec_re_2 = regex::Regex::new(r#"_json_vec__.unwrap_or_default\(\)(),"#).unwrap();

    for path in [
        "./capture.serde.rs",
        "./derive.serde.rs",
        "./flow.serde.rs",
        "./materialize.serde.rs",
        "./ops.serde.rs",
    ] {
        let root = &b.src_dir;
        let mut buf = std::fs::read_to_string(&root.join(path)).unwrap();

        // Handle _json fields.
        while let Some(capture) = ser_json_re.captures(&buf) {
            let range = capture.get(1).unwrap().range();
            let field = &buf[range.clone()];
            buf.replace_range(range, &format!("crate::as_raw_json({field})?"));
        }
        while let Some(capture) = de_json_re_1.captures(&buf) {
            let range = capture.get(1).unwrap().range();
            buf.replace_range(
                range,
                &format!(": Option<Box<serde_json::value::RawValue>> = None"),
            );
        }
        while let Some(capture) = de_json_re_2.captures(&buf) {
            let range = capture.get(1).unwrap().range();
            buf.replace_range(
                range,
                &format!("map(|r| Box::<str>::from(r).into()).unwrap_or_default()"),
            );
        }

        // Handle _json_map fields.
        while let Some(capture) = ser_json_map_re.captures(&buf) {
            let range = capture.get(1).unwrap().range();
            let field = &buf[range.clone()];
            buf.replace_range(range, &format!("&crate::as_raw_json_map({field})?"));
        }
        while let Some(capture) = de_json_map_re_1.captures(&buf) {
            let range = capture.get(1).unwrap().range();
            buf.replace_range(
                range,
                &format!(": Option<std::collections::BTreeMap<String, Box<serde_json::value::RawValue>>> = None"),
            );
        }
        while let Some(capture) = de_json_map_re_2.captures(&buf) {
            let range = capture.get(1).unwrap().range();
            buf.replace_range(
                range,
                &format!(".into_iter().map(|(field, value)| (field, Box::<str>::from(value).into())).collect()"),
            );
        }

        // Handle _json_vec fields.
        while let Some(capture) = ser_json_vec_re.captures(&buf) {
            let range = capture.get(1).unwrap().range();
            let field = &buf[range.clone()];
            buf.replace_range(range, &format!("&crate::as_raw_json_vec({field})?"));
        }
        while let Some(capture) = de_json_vec_re_1.captures(&buf) {
            let range = capture.get(1).unwrap().range();
            buf.replace_range(
                range,
                &format!(": Option<Vec<Box<serde_json::value::RawValue>>> = None"),
            );
        }
        while let Some(capture) = de_json_vec_re_2.captures(&buf) {
            let range = capture.get(1).unwrap().range();
            buf.replace_range(
                range,
                &format!(".into_iter().map(|value| Box::<str>::from(value).into()).collect()"),
            );
        }

        // Handle serializing "bytesTotal"/"bytes" as an integer rather than a quoted integer.
        while let Some(capture) = ser_bytes_re.captures(&buf) {
            let range = capture.get(0).unwrap().range();
            buf.replace_range(
                range,
                &format!(
                    r#"struct_ser.serialize_field("{}", &self.{})?;"#,
                    capture.get(1).unwrap().as_str(),
                    capture.get(2).unwrap().as_str(),
                ),
            );
        }

        std::fs::write(&root.join(path), buf).unwrap();
    }
}

#[cfg(not(feature = "generate"))]
fn main() {}
